package top.rainbowecho.upload.web.controller;

import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.fdfs.ThumbImageConfig;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.google.common.base.Stopwatch;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import top.rainbowecho.common.exception.WrongTokenTypeException;
import top.rainbowecho.upload.application.command.BlogFsService;
import top.rainbowecho.upload.config.FsProperties;
import top.rainbowecho.upload.config.UploadConstants;
import top.rainbowecho.upload.domain.model.fs.vo.UploadResult;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author rainbow
 * @since 2019/12/7 12:58
 */
@RestController
@RequestMapping(UploadConstants.API_PREFIX)
@Slf4j
public class UploadController {
    private BlogFsService fsService;

    private FsProperties fsProperties;

    private ThumbImageConfig thumbImageConfig;

    private FastFileStorageClient normalStoreClient;

    @Autowired
    public void setNormalStoreClient(FastFileStorageClient normalStoreClient) {
        this.normalStoreClient = normalStoreClient;
    }

    @Autowired
    public void setThumbImageConfig(ThumbImageConfig thumbImageConfig) {
        this.thumbImageConfig = thumbImageConfig;
    }

    @Autowired
    public void setFsProperties(FsProperties fsProperties) {
        this.fsProperties = fsProperties;
    }

    @Autowired
    public void setFsService(BlogFsService fsService) {
        this.fsService = fsService;
    }

    @GetMapping(UploadConstants.HELLO_URI)
    @HystrixCommand(fallbackMethod = "defaultHello",commandProperties =
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"))
    public ResponseEntity<String> hello(String name, @RequestHeader(CommonKey.OWN_AUTHORIZATION) String jwtToken) throws InterruptedException, WrongTokenTypeException {
        if (!StringUtils.equals(name, "upload")) {
            Thread.sleep(6000);
        }
        String username = CommonUtil.obtainInfoFromToken(jwtToken, JwtTokenKey.USER_NAME);

        return new ResponseEntity<>("hello, gateway to upload success " + username, HttpStatus.OK);
    }

    private ResponseEntity<String> defaultHello(String name, @RequestHeader(CommonKey.OWN_AUTHORIZATION) String jwtToken) throws InterruptedException {
        return new ResponseEntity<>("default hello to gateway", HttpStatus.OK);
    }

    /**
     * 上传图片文件，不生成缩略图
     *
     * @param file 上传的文件
     * @param jwtToken 用户的token
     * @return 返回图片的访问路径以及过期时间
     * @throws InterruptedException 线程中断
     * @throws ExecutionException 执行超时
     * @throws TimeoutException 超时
     * @throws IOException 文件读取异常
     */
    @PostMapping(UploadConstants.IMAGE_UPLOAD)
    @HystrixCommand(fallbackMethod = "defaultUploadImage",commandProperties =
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"))
    public ResponseEntity<Map<String, Object>> uploadImage(MultipartFile file, @RequestHeader(CommonKey.OWN_AUTHORIZATION) String jwtToken) throws IOException, InterruptedException, ExecutionException, TimeoutException, WrongTokenTypeException {
        String username = CommonUtil.obtainInfoFromToken(jwtToken, JwtTokenKey.USER_NAME);

        // 上传文件到Fs系统，并通过MQ向AI service发送消息，进行文件的审核
        Stopwatch started = Stopwatch.createStarted();
        UploadResult imageUploadResult = fsService.uploadFile(file, username, (is, extension) -> CompletableFuture.supplyAsync(() ->{
            try {
                return normalStoreClient.uploadFile(is, is.available(), extension, null).getFullPath();
            } catch (IOException e) {
                log.error(e.getMessage());
                return null;
            }
        }));
        long duration = started.elapsed(TimeUnit.MILLISECONDS);
        log.info("上传图片并识别共用时(ms): " + duration);

        return returnTokenResponse(imageUploadResult);
    }

    private ResponseEntity<Map<String, Object>> defaultUploadImage(MultipartFile file, @RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
        Map<String, Object> info = new ResultParamFactory()
                .add(ResultKey.MESS, ExceptionMessageContent.UPLOAD_RECOGNIZE_TIMEOUT)
                .build();
        return new ResponseEntity<>(info, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * 与图片上传相同，区别在于在于返回的访问路径是缩略图访问路径，但是可以通过缩略图访问路径得到全图访问路径
     *
     * @param file 上传的文件
     * @param jwtToken 用户携带的token
     * @return 返回缩略图的访问路径以及过期时间
     * @throws InterruptedException 线程中断
     * @throws ExecutionException 执行超时
     * @throws TimeoutException 超时
     * @throws IOException 文件读取失败
     */
    @PostMapping(UploadConstants.THUMB_UPLOAD)
    public ResponseEntity<Map<String, Object>> uploadThumb(MultipartFile file, @RequestHeader(CommonKey.OWN_AUTHORIZATION) String jwtToken)
            throws InterruptedException, ExecutionException, TimeoutException, IOException, WrongTokenTypeException {
        String username = CommonUtil.obtainInfoFromToken(jwtToken, JwtTokenKey.USER_NAME);

        UploadResult thumbUploadResult = fsService.uploadFile(file, username, (is, extension) -> CompletableFuture.supplyAsync(() ->{
            try {
                StorePath storePath = normalStoreClient.uploadImageAndCrtThumbImage(is, is.available(), extension, null);

                return String.format("%s/%s", storePath.getGroup(), thumbImageConfig.getThumbImagePath(storePath.getPath()));
            } catch (IOException e) {
                log.error(e.getMessage());
                return null;
            }
        }));

        return returnTokenResponse(thumbUploadResult);
    }

    /**
     * 根据提交的文件的全路径，旧token以及旧的时间戳，生成新的token
     *
     * @param fullPath 文件在Fs中的全路径
     * @param token 旧图片token
     * @param ts 旧时间戳
     * @return 如果旧token有效，则返回新的图片访问路径以及过期时间；如果无效，则返回授权失败提示
     */
    @PutMapping(UploadConstants.TOKEN_REFRESH)
    public ResponseEntity<Map<String, Object>> refreshToken(String fullPath, String token, int ts) {
        // 刷新之前需要验签
        String trueToken = CommonUtil.generateToken(fullPath, fsProperties.getSecretKey(), ts);

        // 验签成功
        if (StringUtils.equals(token, trueToken)) {
            String newVisitUri = fsService.generateToken(fullPath);
            // 能够刷新，必然已经通过了审核，因此不需要再审核图片
            UploadResult uploadResult = new UploadResult(false, newVisitUri);
            return returnTokenResponse(uploadResult);
        } else {
            return new ResponseEntity<>(new HashMap<>(), HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * 用户删除图片
     *
     * @param fullPath 文件的全路径
     * @return 删除提示
     */
    @DeleteMapping(UploadConstants.DELETE_FILE)
    public ResponseEntity<Map<String, Object>> deleteFile(String fullPath) {
        normalStoreClient.deleteFile(fullPath);
        return new ResponseEntity<>(new HashMap<>(), HttpStatus.OK);
    }

    private ResponseEntity<Map<String, Object>> returnTokenResponse(UploadResult uploadResult) {
        Map<String, Object> params = new ResultParamFactory().add(ResultKey.UPLOAD_RESULT, uploadResult)
                .add(ResultKey.EXPIRE, fsProperties.getExpire())
                .build();

        return new ResponseEntity<>(params, HttpStatus.OK);
    }
}
